#include "router.h"
#include "EventLoop.h"
#include "HttpClient.h"
#include "db/eventrecords.h"
#include "devices_collect.h"
#include "handler.h"
#include "hasync.h"  // import hv::async
#include "hlog.h"
#include "hstring.h"
#include "hthread.h"  // import hv_gettid
#include "json.hpp"
#include "msgdef.h"
#include "requests.h"  // import requests::async
#include "utils/auth.h"
#include "utils/st.h"
#include <chrono>
#include <iomanip>

using json = nlohmann::json;
using namespace zx;

std::map<int, std::string> event_type = {
    {1,   "DRIVER_LEFT_SEAT"},
    {10,  "YAWN"            },
    {13,  "TURN_HEAD"       },
    {14,  "HEAD_LOW"        },
    {15,  "CLOSE_EYE"       },
    {20,  "DOZE"            },
    {50,  "SENSOR_COVER"    },
    {257, "NO_BELT"         },
    {258, "SMOKING"         },
    {259, "USE_PHONE"       },
    {260, "ALARM_OPEN_DOOR" },
    {301, "car_normal"      },
    {302, "car_warn"        },
    {303, "car_autobrake"   },
    {316, "proximity_normal"},
    {317, "proximity_warn"  },
    {318, "proximity_danger"},
    {344, "seatbelt_alarm"  },
    {346, "door_ajar"       }
};

bool deepCompare(const json& t1, const json& t2) {
    if (!t1.is_object() || !t2.is_object()) {
        return t1 == t2;
    }

    for (auto it = t1.begin(); it != t1.end(); ++it) {
        if (t2.find(it.key()) == t2.end()) {
            return false;
        }

        if (it.value().is_object()) {
            if (!deepCompare(it.value(), t2[it.key()])) {
                return false;
            }
        } else {
            if (it.key() != "timestamp" && it.key() != "heartbeat" && it.key() != "online") {
                if (it.value() != t2[it.key()]) {
                    return false;
                }
            }
        }
    }

    return true;
}

bool makePath(const std::string& path) {
    size_t pos = 0;
    std::string subPath;

    while ((pos = path.find_first_of('/', pos)) != std::string::npos) {
        subPath = path.substr(0, pos++);
        if (subPath.empty()) continue;  // 跳过开头的斜杠

        if (mkdir(subPath.c_str(), S_IRWXU) != 0 && errno != EEXIST) {
            hloge("mkdir error: %s", subPath.c_str());
            return false;
        }
    }

    if (mkdir(path.c_str(), S_IRWXU) != 0 && errno != EEXIST) {
        hloge("mkdir error: %s", subPath.c_str());
        return false;
    }

    return true;
}

void Router::car_heartbeat_check(hv::TimerID timerID, std::unordered_map<std::string, nlohmann::json>& collect) {
    static int _first = true;
    for (auto it = collect.begin(); it != collect.end(); ++it) {
        auto [ret, et] = StFactory::instance()->ton(it->first)(collect[it->first]["heartbeat"], 25000);
        if (ret) {
            _first                       = false;
            collect[it->first]["online"] = false;
        } else {
            if (_first) {
                collect[it->first]["online"] = false;
            } else {
                collect[it->first]["online"] = true;
            }
        }
        collect[it->first]["heartbeat"] = false;
    }
}

void Router::Register(hv::HttpService& router, conf_ctx_t* g_conf_ctx) {
    router.preprocessor  = Handler::preprocessor;
    router.postprocessor = Handler::postprocessor;

    // middleware
    // router.Use(Handler::Authorization);

    router.AllowCORS();

    // static route
    // std::string dir = g_conf_ctx->log_path;
    // router.GET("/logs/{path}", [dir](const HttpContextPtr& ctx) { return Handler::route_path(ctx, dir); });

    // dir = g_conf_ctx->records_path;
    // router.GET("/records/{path}", [dir](const HttpContextPtr& ctx) { return Handler::route_path(ctx, dir); });

    // dir = g_conf_ctx->snapshot_path;
    // router.GET("/snapshot/{path}", [dir](const HttpContextPtr& ctx) { return Handler::route_path(ctx, dir); });

    //dist static route
    router.Static("/", g_conf_ctx->dist_path.c_str());
    router.Static("/logs/", g_conf_ctx->log_path.c_str());
    router.Static("/records/", g_conf_ctx->records_path.c_str());
    router.Static("/snapshot/", g_conf_ctx->snapshot_path.c_str());

    // admin api
    router.POST("/admin/user_register", [](HttpRequest* req, HttpResponse* resp) {
        if (req->body.empty()) {
            resp->json = {
                {"code", 401            },
                {"msg",  "parameter err"}
            };
            return 200;
        }
        json req_json = req->json;
        if (!req_json.contains("username") || !req_json.contains("password")) {
            resp->json = {
                {"code", 401                 },
                {"msg",  "missing parameters"}
            };
            return 200;
        }

        std::string username = req_json["username"].get<std::string>();
        std::string password = req_json["password"].get<std::string>();
        bool success         = AuthHandler->user_register(username, password);
        resp->json           = {
            {"code", 200                                           },
            {"msg",  success ? "register success" : "register fail"}
        };

        return 200;
    });

    router.POST("/admin/user_modify", [](HttpRequest* req, HttpResponse* resp) {
        if (req->body.empty()) {
            resp->json = {
                {"code", 401            },
                {"msg",  "parameter err"}
            };
            return 200;
        }
        json req_json = req->json;
        if (!req_json.contains("username") || !req_json.contains("password")) {
            resp->json = {
                {"code", 401                 },
                {"msg",  "missing parameters"}
            };
            return 200;
        }

        std::string username = req_json["username"].get<std::string>();
        std::string password = req_json["password"].get<std::string>();
        bool success         = AuthHandler->user_modify(username, password);
        resp->json           = {
            {"code", 200                                       },
            {"msg",  success ? "modify success" : "modify fail"}
        };

        return 200;
    });

    router.POST("/admin/user_delete", [](HttpRequest* req, HttpResponse* resp) {
        if (req->body.empty()) {
            resp->json = {
                {"code", 401            },
                {"msg",  "parameter err"}
            };
            return 200;
        }
        json req_json = req->json;
        if (!req_json.contains("username")) {
            resp->json = {
                {"code", 401                 },
                {"msg",  "missing parameters"}
            };
            return 200;
        }

        std::string username = req_json["username"].get<std::string>();
        bool success         = AuthHandler->user_delete(username);
        resp->json           = {
            {"code", 200                                       },
            {"msg",  success ? "delete success" : "delete fail"}
        };

        return 200;
    });

    router.GET("/admin/user_get", [](HttpRequest* req, HttpResponse* resp) {
        auto vec = AuthHandler->user_get();

        json jsonData;
        for (size_t i = 0; i < vec.size(); ++i) {
            jsonData["numbers"].push_back(vec[i]);
        }

        resp->json = {
            {"code", 200     },
            {"msg",  jsonData}
        };
        return 200;
    });

    router.GET("/admin/events/delete", [](HttpRequest* req, HttpResponse* resp) {
        std::string id = req->GetParam("id");
        if (id.empty()) {
            resp->json = {
                {"code", 401                 },
                {"msg",  "missing parameters"}
            };
        } else {
            EventRecordsHandler->events_delete(id);
            resp->json = {
                {"code", 200      },
                {"msg",  "success"}
            };
        }
        return 200;
    });

    router.GET("/admin/events/set_invalid", [](HttpRequest* req, HttpResponse* resp) {
        std::string id = req->GetParam("id");
        if (id.empty()) {
            resp->json = {
                {"code", 401                 },
                {"msg",  "missing parameters"}
            };
        } else {
            EventRecordsHandler->events_set_invalid(id);
            resp->json = {
                {"code", 200      },
                {"msg",  "success"}
            };
        }
        return 200;
    });

    // czgw api
    router.POST("/cars/register", [](HttpRequest* req, HttpResponse* resp) {
        if (req->body.empty()) {
            resp->json = {
                {"code", 401            },
                {"msg",  "parameter err"}
            };
            return 200;
        }
        json req_json = req->json;
        if (!req_json.contains("plate")) {
            resp->json = {
                {"code", 401                 },
                {"msg",  "missing parameters"}
            };
            return 200;
        }
        std::string plate = req_json["plate"].get<std::string>();
        auto it           = device_collect.find(plate);
        if (it == device_collect.end()) {
            device_collect["plate"] = json::object();
        }

        json& device        = device_collect["plate"];
        device["heartbeat"] = true;

        if (!deepCompare(req_json, device)) {
            // hlogi("new device register: %s, %s", plate.c_str(), req_json.dump().c_str());
            device = req_json;

            cars_t item;
            item.plate = plate;
            if (req_json.contains("attr")) {
                item.attr = req_json["attr"].get<std::string>();
            }
            item.devices    = req_json["devices"].dump();
            item.department = req_json["department"].get<std::string>();
            item.ip_addr    = req_json["ip_addr"].get<std::string>();
            if (req_json.contains("timestamp")) item.timestamp = req_json["timestamp"].get<uint64_t>();
            if (req_json.contains("name")) {
                item.user = req_json["name"].get<std::string>();
            }
            EventRecordsHandler->cars_add(item);
        }
        resp->json = {
            {"code", 200          },
            {"msg",  "add success"}
        };
        return 200;
    });

    router.POST("/cars/events/add", [g_conf_ctx](const HttpContextPtr& ctx) {
        int status_code = 200;
        if (!ctx->is(MULTIPART_FORM_DATA)) {
            ctx->response->json = {
                {"code",    401                 },
                {"message", "param format error"}
            };
            return status_code;
        }

        events_t ev;
        ev.type      = stoi(ctx->request->GetFormData("type"));
        ev.plate     = ctx->request->GetFormData("plate");
        ev.level     = stoi(ctx->request->GetFormData("level"));
        ev.content   = ctx->request->GetFormData("content");
        ev.timestamp = stoull(ctx->request->GetFormData("timestamp"));

        auto now           = std::chrono::system_clock::now();
        std::time_t now_c  = std::chrono::system_clock::to_time_t(now);
        std::tm* localTime = std::localtime(&now_c);
        char dateBuffer[20];
        std::strftime(dateBuffer, sizeof(dateBuffer), "/%Y-%m-%d/", localTime);
        std::string currentDate(dateBuffer);
        char timeBuffer[15];
        std::strftime(timeBuffer, sizeof(timeBuffer), "%H-%M-%S_", localTime);
        std::string currentTime(timeBuffer);

        std::string file_dir = g_conf_ctx->snapshot_path + ev.plate + currentDate;

        std::string ev_type;
        auto it = event_type.find(ev.type);
        if (it == event_type.end()) {
            ev_type = "unknown";
        } else {
            ev_type = event_type[ev.type];
        }
        ev.imagepath = file_dir + currentTime + ev_type + ".jpg";
        ev.videopath = file_dir + currentTime + ev_type + ".mp4";
        makePath(file_dir);

        status_code = ctx->request->SaveFormFile("image", ev.imagepath.c_str());
        status_code = ctx->request->SaveFormFile("video", ev.videopath.c_str());

        EventRecordsHandler->events_add(ev);

        ctx->response->json = {
            {"code",    200      },
            {"message", "success"}
        };
        return status_code;
    });

    router.POST("/cars/records/upload", [g_conf_ctx](const HttpContextPtr& ctx) {
        int status_code = 200;
        if (!ctx->is(MULTIPART_FORM_DATA)) {
            ctx->response->json = {
                {"code",    401                 },
                {"message", "param format error"}
            };
            return status_code;
        }

        records_t rec;
        rec.plate      = ctx->request->GetFormData("plate");
        rec.position   = ctx->request->GetFormData("position");
        rec.path       = ctx->request->GetFormData("path");
        rec.start_time = stoull(ctx->request->GetFormData("start_time"));
        rec.end_time   = stoull(ctx->request->GetFormData("end_time"));
        rec.timestamp  = stoull(ctx->request->GetFormData("timestamp"));

        auto now           = std::chrono::system_clock::now();
        std::time_t now_c  = std::chrono::system_clock::to_time_t(now);
        std::tm* localTime = std::localtime(&now_c);
        char dateBuffer[20];
        std::strftime(dateBuffer, sizeof(dateBuffer), "/%Y-%m-%d/", localTime);
        std::string currentDate(dateBuffer);
        char timeBuffer[15];
        std::strftime(timeBuffer, sizeof(timeBuffer), "%H-%M-%S", localTime);
        std::string currentTime(timeBuffer);

        std::string file_dir = g_conf_ctx->records_path + rec.plate + "/" + rec.position + currentDate;
        makePath(file_dir);

        std::string filename = file_dir + currentTime + ".mp4";
        status_code          = ctx->request->SaveFormFile("video", filename.c_str());

        EventRecordsHandler->records_add(rec);
        ctx->response->json = {
            {"code",    200      },
            {"message", "success"}
        };
        return status_code;
    });

    // user api
    router.POST("/platform/login", [](HttpRequest* req, HttpResponse* resp) {
        if (req->body.empty()) {
            resp->json = {
                {"code", 401            },
                {"msg",  "parameter err"}
            };
            return 200;
        }
        json req_json = req->json;
        if (!req_json.contains("username") || !req_json.contains("password")) {
            resp->json = {
                {"code", 401                 },
                {"msg",  "missing parameters"}
            };
            return 200;
        }

        std::string username = req_json["username"].get<std::string>();
        std::string password = req_json["password"].get<std::string>();
        bool success         = AuthHandler->login(username, password);
        resp->json           = {
            {"code", 200                                     },
            {"msg",  success ? "login success" : "login fail"}
        };
        return 200;
    });

    router.POST("/user/logout", [](HttpRequest* req, HttpResponse* resp) {
        if (req->body.empty()) {
            resp->json = {
                {"code", 401            },
                {"msg",  "parameter err"}
            };
            return 200;
        }
        json req_json = req->json;
        if (!req_json.contains("username")) {
            resp->json = {
                {"code", 401                 },
                {"msg",  "missing parameters"}
            };
            return 200;
        }

        std::string username = req_json["username"].get<std::string>();
        bool success         = AuthHandler->logout(username);
        resp->json           = {
            {"code", 200                                       },
            {"msg",  success ? "logout success" : "logout fail"}
        };
        return 200;
    });

    router.POST("/user/cars/get", [](HttpRequest* req, HttpResponse* resp) {
        cars_t car;
        if (req->json.contains("plate")) car.plate = req->json["plate"].get<std::string>();
        if (req->json.contains("user")) car.user = req->json["user"].get<std::string>();
        if (req->json.contains("department")) car.department = req->json["department"].get<std::string>();
        int page_size     = req->json["page_size"].get<int>();
        int page_number   = req->json["page_number"].get<int>();
        cars_query_t cars = EventRecordsHandler->cars_get(car, page_number, page_size);
        json jsonArr      = json::array();
        for (const auto& elem : cars.data) {
            json eventJson;
            eventJson["department"] = elem.department;
            eventJson["user"]       = elem.user;
            eventJson["plate"]      = elem.plate;
            eventJson["timestamp"]  = elem.timestamp;
            eventJson["attr"]       = elem.attr;
            eventJson["ip_addr"]    = elem.ip_addr;
            eventJson["devices"]    = elem.devices;
            jsonArr.push_back(eventJson);
        }
        resp->json = {
            {"code", 200                                       },
            {"msg",  {{"count", cars.count}, {"data", jsonArr}}}
        };
        return 200;
    });

    router.POST("/user/events/get", [](HttpRequest* req, HttpResponse* resp) {
        int min_type   = req->json["min_type"].get<int>();
        int max_type   = req->json["max_type"].get<int>();
        uint64_t start = req->json["start_time"].get<uint64_t>();
        uint64_t end   = req->json["end_time"].get<uint64_t>();
        int page_num   = req->json["page_number"].get<int>();
        int page_size  = req->json["page_size"].get<int>();
        std::string plate;
        int level;

        if (req->json.contains("plate")) plate = req->json["plate"].get<std::string>();
        if (req->json.contains("level")) level = req->json["level"].get<int>();

        event_query_t e_query =
            EventRecordsHandler->events_get(min_type, max_type, plate, level, start, end, page_num, page_size);

        json jsonArr = json::array();
        for (const auto& elem : e_query.data) {
            json eventJson;
            eventJson["id"]        = elem.id;
            eventJson["content"]   = elem.content;
            eventJson["imagepath"] = elem.imagepath;
            eventJson["level"]     = elem.level;
            eventJson["plate"]     = elem.plate;
            eventJson["timestamp"] = elem.timestamp;
            eventJson["type"]      = elem.type;
            eventJson["videopath"] = elem.videopath;
            jsonArr.push_back(eventJson);
        }
        resp->json = {
            {"code", 200                                          },
            {"msg",  {{"count", e_query.count}, {"data", jsonArr}}}
        };
        return 200;
    });

    router.POST("/user/events/statistics", [](HttpRequest* req, HttpResponse* resp) {
        static struct record_cache {
            time_t last_time = 0;
            json result;
        } res_cache;

        time_t now = std::time(nullptr);
        if (now - res_cache.last_time > 10 || res_cache.result.empty()) {
            res_cache.last_time = now;
            std::string plate;

            if (req->json.contains("plate")) plate = req->json["plate"].get<std::string>();
            int min_type   = req->json["min_type"].get<int>();
            int max_type   = req->json["max_type"].get<int>();
            uint64_t start = req->json["start_time"].get<uint64_t>();
            uint64_t end   = req->json["end_time"].get<uint64_t>();

            auto pie_map = EventRecordsHandler->statistics_count(min_type, max_type, plate, start, end);
            json json_pie;
            for (const auto& pair : pie_map) {
                json_pie[std::to_string(pair.first)] = pair.second;
            }

            std::string line_map = EventRecordsHandler->statistics_get(min_type, max_type, plate, start, end);

            res_cache.result = {
                {"pie_chart",  json_pie},
                {"line_chart", line_map}
            };
        }

        resp->json = {
            {"code", 200             },
            {"msg",  res_cache.result}
        };
        return 200;
    });

    router.POST("/user/records/get", [](HttpRequest* req, HttpResponse* resp) {
        std::string plate      = req->json["plate"].get<std::string>();
        std::string position   = req->json["position"].get<std::string>();
        int page_number        = req->json["page_number"].get<int>();
        int page_size          = req->json["page_size"].get<int>();
        uint64_t start         = req->json["start_time"].get<uint64_t>();
        uint64_t end           = req->json["end_time"].get<uint64_t>();
        records_query_t record = EventRecordsHandler->records_get(plate, position, start, end, page_number, page_size);
        json jsonArr           = json::array();
        for (const auto& elem : record.data) {
            json eventJson;
            eventJson["path"]       = elem.path;
            eventJson["position"]   = elem.position;
            eventJson["start_time"] = elem.start_time;
            eventJson["end_time"]   = elem.end_time;
            eventJson["plate"]      = elem.plate;
            eventJson["timestamp"]  = elem.timestamp;
            jsonArr.push_back(eventJson);
        }
        resp->json = {
            {"code", 200                                         },
            {"msg",  {{"count", record.count}, {"data", jsonArr}}}
        };
        return 200;
    });

    router.POST("/user/cars/set_cam", [g_conf_ctx](HttpRequest* req, HttpResponse* resp) {
        std::string plate = req->json["plate"].get<std::string>();

        char url[128]   = {0};
        json plate_json = device_collect[plate];
        if (plate_json.empty()) {
            resp->json = {
                {"code", 401                  },
                {"msg",  "device is not exits"}
            };
            return 200;
        }

        std::snprintf(url,
                      sizeof(url),
                      "http://%s:%d/api/platform/set_cam",
                      plate_json["ip_addr"].get<std::string>().c_str(),
                      g_conf_ctx->czgw_port);

        HttpRequest req0;
        req0.method  = HTTP_POST;
        req0.url     = url;
        req0.body    = req->json.dump();
        req0.timeout = 10;
        HttpResponse resp0;
        hv::HttpClient cli;
        int ret = cli.send(&req0, &resp0);
        if (ret != 0) {
            hloge("set_cam request failed!\n");
            resp->status_code = (http_status)401;
        } else {
            resp->body        = resp0.body;
            resp->status_code = resp0.status_code;
        }
        return 200;
    });

    router.POST("/user/cars/request_upload_video", [g_conf_ctx](HttpRequest* req, HttpResponse* resp) {
        std::string plate = req->json["plate"].get<std::string>();

        char url[128]   = {0};
        json plate_json = device_collect[plate];
        if (plate_json.empty()) {
            resp->json = {
                {"code", 401                  },
                {"msg",  "device is not exits"}
            };
            return 200;
        }
        std::snprintf(url,
                      sizeof(url),
                      "http://%s:%d/api/platform/request_upload_video",
                      plate_json["ip_addr"].get<std::string>().c_str(),
                      g_conf_ctx->czgw_port);

        HttpRequest req0;
        req0.method  = HTTP_POST;
        req0.url     = url;
        req0.body    = req->json["video_id"].dump();
        req0.timeout = 10;
        HttpResponse resp0;
        hv::HttpClient cli;
        int ret = cli.send(&req0, &resp0);
        if (ret != 0) {
            hloge("request_upload_video request failed!\n");
            resp->status_code = (http_status)401;
        } else {
            resp->body        = resp0.body;
            resp->status_code = resp0.status_code;
        }
        return 200;
    });

    router.POST("/user/cars/get_unupload_video", [g_conf_ctx](HttpRequest* req, HttpResponse* resp) {
        std::string plate    = req->json["plate"].get<std::string>();
        std::string position = req->json["position"].get<std::string>();
        int page_number      = req->json["page_number"].get<int>();
        int page_size        = req->json["page_size"].get<int>();
        uint64_t start       = req->json["start_time"].get<uint64_t>();
        uint64_t end         = req->json["end_time"].get<uint64_t>();

        char url[128]   = {0};
        json plate_json = device_collect[plate];

        if (plate_json.empty()) {
            resp->json = {
                {"code", 401                  },
                {"msg",  "device is not exits"}
            };
            return 200;
        }
        std::snprintf(url,
                      sizeof(url),
                      "http://%s:%d/api/platform/get_unupload_video",
                      plate_json["ip_addr"].get<std::string>().c_str(),
                      g_conf_ctx->czgw_port);

        HttpRequest req0;
        req0.method  = HTTP_POST;
        req0.url     = url;
        req0.body    = req->json.dump();
        req0.timeout = 10;
        HttpResponse resp0;
        hv::HttpClient cli;
        int ret = cli.send(&req0, &resp0);
        if (ret != 0) {
            hloge("get_unupload_video request failed!\n");
            resp->status_code = (http_status)401;
        } else {
            resp->body        = resp0.body;
            resp->status_code = resp0.status_code;
        }
        return 200;
    });
}
